Articles & Books

Function overloading is more flexible than template function specialization -- Raymond Chen

RaymondChen_5in-150x150.jpgWhen trying to specialize a templated function for specific types, it’s easy to fall into subtle traps around how parameter types are matched. A colleague recently ran into this issue while attempting to specialize a function for a Widget and a string literal, only to be met with confusing compiler errors that hinted at deeper quirks in C++'s type deduction and function template specialization rules.

Function overloading is more flexible (and more convenient) than template function specialization

by Raymond Chen

From the article:

A colleague of mine was having trouble specializing a templated function. Here’s a simplified version.

template<typename T, typename U>
bool same_name(T const& t, U const& u)
{
    return t.name() == u.name();
}

They wanted to provide a specialization for the case that the parameters are a Widget and a string literal.

template<>
bool same_name<Widget, const char[]>(Widget const& widget, const char name[])
{
    return strcmp(widget.descriptor().name().c_str(), name) == 0;
}

However, this failed to compile:

// msvc
error C2912: explicit specialization 'bool 
same_name<Widget,const char[]>(const Widget &,const char 
[])' is not a specialization of a function template

What do you mean “not a specialization of a function template”? I mean doesn’t it look like a specialization of a function template? It sure follows the correct syntax for a function template specialization.

C++26: More constexpr in the Core Language -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGIn this article, we review how constexpr evolves in the C++26 core language. We are getting constexpr cast from void*, placement new, structured bindings and even exceptions (not discussed today). 

C++26: More constexpr in the Core Language

by Sandor Dargo

From the article:

Since constexpr was added to the language in C++11, its scope has been gradually expanded. In the beginning, we couldn’t even use ifelse or loops, which were changed in C++14. C++17 added support for constexpr lambdas. C++20 added the ability to use allocation and use std::vector and std::string in constant expressions. In this article, let’s see how constexpr evolves with C++26. To be more punctual, let’s see what language features become more constexpr-friendly. We’ll discuss library changes in a separate article, as well as constexpr exceptions, which need both language and library changes.

P2738R1constexpr cast from void*

Thanks to the acceptance of P2738R1, starting from C++26, one can cast from void* to a pointer of type T in constant expressions, if the type of the object at that adress is exactly the type of T.

Note that conversions to interconvertible - including pointers to base classes - or not related types are not permitted.

The motivation behind this change is to make several standard library functions or types work at compile time. To name a few examples: std::formatstd::functionstd::function_refstd::any. The reason why this change will allow many more for more constexpr in the standard library is that storing void* is a commonly used compilation firewall technique to reduce template instantiations and the number of symbols in compiled binaries.

Streamlined Iteration: Exploring Keys and Values in C++20 -- Daniel Lemire

image-27-825x510.jpgModern C++ offers a variety of ways to work with key-value data structures like std::map and std::unordered_map, from traditional loops to sleek functional-style expressions using C++20 ranges. By exploring both styles and benchmarking them across platforms, we can better understand how newer language features affect readability, expressiveness, and performance.

Streamlined Iteration: Exploring Keys and Values in C++20

by Daniel Lemire

From the article:

In software, we often use key-value data structures, where each key is unique and maps to a specific value. Common examples include dictionaries in Python, hash maps in Java, and objects in JavaScript. If you combine arrays with key-value data structures, you can represent most data.

In C++, we have two standard key-value data structures: the std::map and the std::unordered_map. Typically, the std::map is implemented as a tree (e.g., a red-black tree) with sorted keys, providing logarithmic time complexity O(log n) for lookups, insertions, and deletions, and maintaining keys in sorted order. The std::unordered_map is typically implemented as a hash table with unsorted keys, offering average-case constant time complexity O(1) for lookups, insertions, and deletions. In the std::unordered_map, the hash table uses a hashing function to map keys to indices in an array of buckets. Each bucket is essentially a container that can hold multiple key-value pairs, typically implemented as a linked list. When a key is hashed, the hash function computes an index corresponding to a bucket. If multiple keys hash to the same bucket (a collision), they are stored in that bucket’s linked list.

Quite often, we only need to look at the keys, or look at the values. The C++20 standard makes this convenient through the introduction of ranges (std::ranges::views::keys and std::ranges::views::values). Let us consider two functions using the ‘modern’ functional style. The first function sums the values and the next function counts how many keys (assuming that they are strings) start with a given prefix.

Owning and non-owning C++ Ranges -- Hannes Hauswedell

This is the first article in a series discussing some of the underlying properties of C++ ranges and in particular range adaptors. At the same time, I introduce the design of an experimental library which aims to solve some of the problems discussed here.

Owning and non-owning C++ Ranges

by Hannes Hauswedell

From the article:

We will begin by having a look at ranges from the standard library prior to C++20, since this is what people are most used to. Note that although the ranges themselves are from C++17, I will use some terminology/concepts/algorithms introduced later to explain how they relate to each other. Remember that to count as a range in C++, a type needs to have just begin() and end(). Everything else is bonus.

[…]

Containers are the ranges everybody already used before Ranges were a thing. They own their elements, i.e. the storage of the elements is managed by the container and the elements disappear when the container does. Containers are multi-pass ranges, i.e. you can iterate over them multiple times and will always observe the same elements.

[…]

If containers are owning ranges, what are non-owning ranges? C++17 introduced a first example: std::string_view, a range that consists just of a begin and end pointer into another range’s elements.

[…]

However, the most important (and controversial) change came by way of P2415, which allowed views to become owning ranges. It was also applied to C++20 as a defect report, although it was quite a significant design change. This is a useful feature, however, it resulted in the std::ranges::view concept being changed to where it no longer means “non-owning range”.

[…]

 

 

How to break or continue from a lambda loop? -- Vittorio Romeo

Is it possible to write a simple iteration API that hides implementation details and lets users break and continue?

Here's a new article about a lightweight solution using a `ControlFlow` enumeration!

How to break or continue from a lambda loop?

by Vittorio Romeo

From the article:

Here’s an encapsulation challenge that I frequently run into: how to let users iterate over an internal data structure without leaking implementation details, but still giving them full control over the loop?

Implementing a custom iterator type requires significant boilerplate and/or complexity, depending on the underlying data structure.

Coroutines are simple and elegant, but the codegen is atrocious – definitely unsuitable for hot paths.

 

Raw Loops for Performance? -- Sandor Dargo

SANDOR_DARGO_ROUND.JPGUsing ranges or algorithms has several advantages over raw loops, notably readability. On the other hand, as we’ve just seen, sheer performance is not necessarily among those advantages. Using ranges can be slightly slower than a raw loop version. But that’s not necessarily a problem, it really depends on your use case. Most probably it won’t make a bit difference.

Raw Loops for Performance?

by Sandor Dargo

From the article:

To my greatest satisfaction, I’ve recently joined a new project. I started to read through the codebase before joining and at that stage, whenever I saw a possibility for a minor improvement, I raised a tiny pull request. One of my pet peeves is rooted in Sean Parent’s 2013 talk at GoingNative, Seasoning C++ where he advocated for no raw loops.

When I saw this loop, I started to think about how to replace it:

2025-05-08_16-46-14.png

Please note that the example is simplified and slightly changed so that it compiles on its own.

Let’s focus on foo, the rest is there just to make the example compilable.

It seems that we could use std::transform. But heck, we use C++20 we have ranges at our hands so let’s go with std::ranges::transform!

constexpr Functions: Optimization vs Guarantee -- Andreas Fertig

Depositphotos_193487484_S.jpgConstexpr has been around for a while now, but many don’t fully understand its subtleties. Andreas Fertig explores its use and when a constexpr expression might not be evaluated at compile time.

constexpr Functions: Optimization vs Guarantee

by Andreas Fertig

From the article:

The feature of constant evaluation is nothing new in 2023. You have constexpr available since C++11. Yet, in many of my classes, I see that people still struggle with constexpr functions. Let me shed some light on them.

What you get is not what you see

One thing, which is a feature, is that constexpr functions can be evaluated at compile-time, but they can run at run-time as well. That evaluation at compile-time requires all values known at compile-time is reasonable. But I often see that the assumption is once all values for a constexpr function are known at compile-time, the function will be evaluated at compile-time.

I can say that I find this assumption reasonable, and discovering the truth isn’t easy. Let’s consider an example (Listing 1).

constexpr auto Fun(int v)
{
  return 42 / v; ①
}

int main()
{
  const auto f = Fun(6); ②
  return f;              ③
}
Listing 1

The constexpr function Fun divides 42 by a value provided by the parameter v ①. In ②, I call Fun with the value 6 and assign the result to the variable f.

Last, in ③, I return the value of f to prevent the compiler optimizes this program away. If you use Compiler Explorer to look at the resulting assembly, GCC with -O1 brings this down to:

  main:
          mov     eax, 7
          ret

As you can see, the compiler has evaluated the result of 42 / 6, which, of course, is 7. Aside from the final number, there is also no trace at all of the function Fun.

Now, this is what, in my experience, makes people believe that Fun was evaluated at compile-time thanks to constexpr. Yet this view is incorrect. You are looking at compiler optimization, something different from constexpr functions.

C++26: Erroneous Behaviour -- Sandor Dargo

Depositphotos_287607756_S.jpgC++’s undefined behaviour impacts safety. Sandor Dargo explains how and why uninitialised reads will become erroneous behaviour in C++26, rather than being undefined behaviour.

C++26: Erroneous Behaviour

by Sandor Dargo

From the article:

If you pick a random talk at a C++ conference these days, there is a fair chance that the speaker will mention safety at least a couple of times. It’s probably fine like that. The committee and the community must think about improving both the safety situation and the reputation of C++.

If you follow what’s going on in this space, you are probably aware that people have different perspectives on safety. I think almost everybody finds it important, but they would solve the problem in their own way.

A big source of issues is certain manifestations of undefined behaviour. It affects both the safety and the stability of software. I remember that a few years ago when I was working on some services which had to support a 10× growth, one of the important points was to eliminate undefined behaviour as much as possible. One main point for us was to remove uninitialized variables which often lead to crashing services.

Thanks to P2795R5 by Thomas Köppe, uninitialized reads won’t be undefined behaviour anymore – starting from C++26. Instead, they will get a new behaviour called ‘erroneous behaviour’.

The great advantage of erroneous behaviour is that it will work just by recompiling existing code. It will diagnose where you forgot to initialize variables. You don’t have to systematically go through your code and let’s say declare everything as auto to make sure that every variable has an initialized value. Which you probably wouldn’t do anyway.

But what is this new behaviour that on C++ Reference is even listed on the page of undefined behaviour? [CppRef-1] It’s well-defined, yet incorrect behaviour that compilers are recommended to diagnose. Is recommended enough?! Well, with the growing focus on safety, you can rest assured that an implementation that wouldn’t diagnose erroneous behaviour would be soon out of the game.

Some compilers can already identify uninitialized reads – what nowadays falls under undefined behaviour. For example, clang and gcc with -ftrivial-auto-var-init=zero have already offered default initialization of variables with automatic storage duration. This means that the technique to identify these variables is already there. The only thing that makes this approach not practical is that you will not know which variables you failed to initialize.

Instead of default initialization, with erroneous behaviour, an uninitialized object will be initialized to an implementation-specific value. Reading such a value is a conceptual error that is recommended and encouraged to be diagnosed by the compiler. That might happen through warnings, run-time errors, etc.

Writing Senders -- Lucian Radu Teodorescu

2025-05-08_16-35-43.pngIn the December issue of Overload [Teodorescu24], we provided a gentle introduction to senders/receivers, arguing that it is easy to write programs with senders/receivers. Then, in the February issue [Teodorescu25a], we had an article that walked the reader through some examples showing how senders/receivers can be used to introduce concurrency in an application. Both of these articles focused on the end users of senders/receivers. This article focuses on the implementer’s side: what does it take to implement senders?

Writing Senders

by Lucian Radu Teodorescu

From the article:

If people are just using frameworks based on std::execution, they mainly need to care about senders and schedulers. These are user-facing concepts. However, if people want to implement sender-ready abstractions, they also need to consider receivers and operation states – these are implementer-side concepts. As this article mainly focuses on the implementation of sender abstractions, we need to discuss these two concepts in more detail.

A receiver is defined in P2300 as “a callback that supports more than one channel” [P2300R10]. The proposal defines a concept for a receiver, unsurprisingly called receiver. To model this concept, a type needs to meet the following conditions:

  • It must be movable and copyable.
  • It must have an inner type alias named receiver_concept that is equal to receiver_t (or a derived type).
  • std::execution::get_env() must be callable on an object of this type (to retrieve the environment of the receiver).

A receiver is the object that receives the sender’s completion signal, i.e., one of set_value()set_error(), or set_stopped(). As explained in the December 2024 issue [Teodorescu24], a sender may have different value completion types and different error completion types. For example, the same sender might sometimes complete with set_value(int, int), sometimes with set_value(double), sometimes with set_error(std::exception_ptr), sometimes with set_error(std::error_code), and sometimes with set_stopped(). This implies that a receiver must also be able to accept multiple types of completion signals.

The need for completion signatures is not directly visible in the receiver concept. There is another concept that the P2300 proposal defines, which includes the completion signatures for a receiver: receiver_of<Completions>. A type models this concept if it also models the receiver concept and provides functions to handle the completions indicated by Completions. More details on how these completions look will be covered in the example sections.

Using Token Sequences to Iterate Ranges -- Barry Revzin

There was a StackOverflow question recently that led me to want to write a new post about Ranges. Specifically, I wanted to write about some situations in which Ranges do more work than it seems like they should have to. And then what we can do to avoid doing that extra work.

Using Token Sequences to Iterate Ranges

by Barry Revzin

From the article:

The Problem

For the purposes of this post, I’m just going to talk about the very simple problem of:

for (auto elem : r) { 
use(elem); 
} 

In the C++ iterator model, this desugars into something like:

auto __it = r.begin(); 
auto __end = r.end(); 

while (__it != __end) { 
use(*__it); 

++__it; 
} 

I used a while loop here deliberately, because it’s a simpler construct and it lets me write the advance step last.

Now, if you want to customize the behavior of a range, those are your entry points right there. You can change what the initialization phase does (begin() and end()), you can change the check against completeness (__it != __end), you can change the read operation (*__it), and you can change the advance operation (++__it). That’s it. You can’t change the structure of the loop itself.

That alone is enough to offer a pretty large wealth of functionality. It’s a very powerful abstraction.